<?php
declare (strict_types = 1);

namespace App\Utils\Http;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Middleware;
use Hyperf\Context\Context;
use Hyperf\Guzzle\ClientFactory;
use Hyperf\HttpMessage\Cookie\CookieJar;
use Hyperf\Collection\Arr;
use Hyperf\Stringable\Str;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Class Request
 * @package App\Utils\Http
 */
Class Request
{
    /**
     * 配置
     * @var Client
     */
    protected Client $client;

    /**
     * 请求OPTIONS
     * @var array
     */
    protected $options = [];

    public function __construct(
        ClientFactory $clientFactory
    )
    {
        $this->client = $clientFactory->create();
    }

    /**
     * 设置选项
     * @param ?array $options
     * @return $this
     */
    public function setOptions(?array $options):self
    {
        if ($options === null) {
            Context::set(static::class.'_options', []);
            return $this;
        }

        $optionData = $this->getOption();
        $options = array_merge($optionData, $options);
        Context::set(static::class.'_options', $options);
        return $this;
    }

    /**
     * 获取选项
     * @return array
     */
    public function getOption(string $key=null):array
    {
        $options = Context::get(static::class.'_options', []);
        return $key?Arr::get($options, $key, []):$options;
    }

    /**
     * 设置请求头
     * @param array $headers
     * @return $this
     */
    public function setHeaders(array $headers=[])
    {
        if ($optionHeaders = $this->getOption('headers')) {
            $this->setOptions(['headers' => array_merge($optionHeaders, $headers)]);
        } else {
            $this->setOptions(['headers' => $headers]);
        }

        return $this;
    }

    /**
     * 设置Cookie
     * @param array $cookies
     * @return $this
     */
    public function setCookie(array $cookies):self
    {
        if ($optionCookies = $this->getOption('cookies')) {
            $this->setOptions(['cookies' => array_merge($optionCookies, $cookies)]);
        } else {
            $this->setOptions(['cookies' => $cookies]);
        }

        return $this;
    }

    /**
     * 设置超时时间
     * @param int $time
     */
    public function setTimeout(int $time=30):self
    {
        $this->setOptions(['timeout' => $time]);

        return $this;
    }

    /**
     * 设置认证
     * @param array $authorization
     * @return $this
     */
    public function setAuthorization(array $authorization=[])
    {
        $this->setOptions(['auth' => $authorization]);

        return $this;
    }

    /**
     * 设置请求的主体为 multipart/form-data 表单。
     * @param [][] $multipart
     * name: (string, required) 表单字段名称
     * contents: (StreamInterface/resource/string, required) 表单元素中要使用的数据
     * headers: (array) 可选的表单元素要使用的键值对数组
     * filename: (string) 可选的作为要发送的文件名称
     * @return $this
     */
    public function setMultipart($multipart)
    {
        $this->setOptions(['multipart' => $multipart]);

        return $this;
    }

    /**
     * 设置Handler
     * @param callable|null $before
     * @param callable|null $after
     */
    public function setHandler(callable $before = null, callable $after = null)
    {
        $tapMiddleware = Middleware::tap($before, $after);
        $this->setOptions(['handler' => $tapMiddleware($this->client->getConfig('handler'))]);

        return $this;
    }

    /**
     * 设置https异常
     * @param bool $errors
     * @return $this
     */
    public function setHttpErrors(bool $errors=false):self
    {
        $this->setOptions(['http_errors' => $errors]);

        return $this;
    }

    /**
     * GET请求
     * @param string $url
     * @param array $data
     * @return string
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function get(string $url, array $data=[])
    {
        $options = $this->getOptions($url);
        if ($data) {
            $options['query'] = $data;
        }
        return $this->app(function ()use($url, $options){
            $request = $this->client->get($url, $options);
            return $request->getBody()->getContents();
        }, $url, $data, 'GET');
    }

    /**
     * POST请求
     * @param string $url
     * @param $data
     * @param bool $json
     * @return string
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function post(string $url, $data=[], bool $json=false)
    {
        $options = $this->getOptions($url);
        if ($data) {
            if (true === $json) {
                $options['headers']['Content-Type'] = 'application/json';
                $options['json'] = $data;
            } else {
                $options['headers']['Content-Type'] = 'application/x-www-form-urlencoded';
                $options['form_params'] = $data;
            }
        }

        return $this->app(function ()use($url, $options){
            $request = $this->client->post($url, $options);
            return $request->getBody()->getContents();
        }, $url, $data, 'POST');
    }

    /**
     * 表单提交
     * @param $url
     * @param array $data
     * @param array $params
     * @return mixed
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function formPost($url, array $data, array $params=[])
    {
        $options = $this->getOptions($url);
        $multipart = [];
        foreach ($data as $key => $datum) {
            $multipartInfo = [
                'name' => $key,
                'contents' => $datum,
            ];
            if (isset($params[$key])) {
                $multipartInfo = array_merge($multipartInfo, $params[$key]);
            }
            $multipart[] = $multipartInfo;
        }
        $options['multipart'] = $multipart;
//        $options['headers']['Content-Type'] = 'multipart/form-data'; // 加了报错! 艹


        return $this->app(function () use ($url, $options) {
            $request = $this->client->post($url, $options);
            return $request->getBody()->getContents();
        }, $url, $multipart, 'POST');
    }

    /**
     * PUT请求
     * @param string $url
     * @param $data
     * @param bool $json
     */
    public function put(string $url, $data, bool $json=false)
    {
        $options = $this->getOptions($url);
        if ($data) {
            if (true === $json) {
                $options['json'] = $data;
            } else {
                $options['body'] = $data;
            }
        }

        return $this->app(function ()use($url, $options){
            $request = $this->client->put($url, $options);
            return $request->getBody()->getContents();
        }, $url, $data, 'PUT');
    }

    /**
     * DELETE请求
     * @param string $url
     * @param $data
     * @return string
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function delete(string $url, $data, bool $json=false)
    {
        $options = $this->getOptions($url);
        if ($data) {
            if (true === $json) {
                $options['json'] = $data;
            } else {
                $options['query'] = $data;
            }
        }

        return $this->app(function ()use($url, $options){
            $request = $this->client->delete($url, $options);
            return $request->getBody()->getContents();
        }, $url, $data, 'DELETE');
    }

    /**
     * 获取options
     * @param string $url
     * @return array
     */
    protected function getOptions(string $url)
    {
        if (!$this->getOption('handler')) {
            $this->setHandler(function (RequestInterface $request, $options) {
                logs('body', 'curl', [$request->getBody()->getContents()]);
                logs('request_headers', 'curl', $request->getHeaders());
            }, function (RequestInterface $request, $options, $response) {
                $response->then(
                    function (ResponseInterface $response) {
                        logs('response_headers', 'curl', $response->getHeaders());
                    }
                );
            });
        }
        $options = [];
        foreach ($this->getOption() as $name => $option) {
            if ($name === 'cookies') {
                $cookieJar = CookieJar::fromArray($option, parse_url($url)['host']);
                $options[$name] = $cookieJar;
            } else {
                $options[$name] = $option;
            }
        }
        $this->setOptions(null);
        return $options;
    }

    /**
     *
     * @param string $url
     * @param $data
     * @return mixed
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function body(string $url, $data)
    {
        $options = $this->getOptions($url);
        $options['headers']['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
        $options['body'] = $data;

        return $this->app(function ()use($url, $options){
            $request = $this->client->request('POST', $url, $options);
            return $request->getBody()->getContents();
        }, $url, $data, 'POST');
    }

    /**
     * setHandler有问题, 不然可以使用setHandler方法代替
     * @param \Closure $closure
     * @param $url
     * @param $data
     * @param $method
     * @return mixed
     */
    protected function app(\Closure $closure, $url, $data, $method)
    {
        $startTime = microtime(true);

        $response = $closure();
        $runtime    = round(microtime(true) - $startTime, 10);
        logs('url', 'curl', [$method=>$url, 'request_time'=>number_format($runtime, 6).'s']);

        return $response;
    }
}